package com.yyfax.account.service;

import com.google.common.collect.Lists;
import com.jaws.core.common.log.Log;
import com.jaws.core.common.page.Page;
import com.jaws.core.common.util.DateUtil;
import com.yyfax.account.common.enums.DayChangeStateEnum;
import com.yyfax.account.common.exception.AccountExceptionCode;
import com.yyfax.account.common.plugin.PageAdapter;
import com.yyfax.account.common.util.JobContextUtils;
import com.yyfax.account.dao.DayChangeReconDao;
import com.yyfax.account.dao.DayChangeReconDetailDao;
import com.yyfax.account.dao.DayChangeRecordDao;
import com.yyfax.account.domain.DayChangeRecon;
import com.yyfax.account.domain.DayChangeReconDetail;
import com.yyfax.account.domain.DayChangeRecord;
import com.yyfax.account.vo.DayChangeAccountRecordQueryReq;
import com.yyfax.account.vo.DayChangeCustomerFundsQueryReq;
import com.yyfax.account.vo.DayChangeCustomerFundsVo;
import com.yyfax.account.vo.DayChangeDifferenceCountQueryReq;
import com.yyfax.account.vo.DayChangeDifferenceCountVo;
import com.yyfax.account.vo.DayChangeListQueryReq;
import com.yyfax.account.vo.DayChangeListVo;
import com.yyfax.account.vo.DayChangeRecordCountQueryReq;
import com.yyfax.account.vo.DayChangeRecordCountVo;
import com.yyfax.account.vo.JobContext;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * 日切对账
 *
 * @author yangrd
 * @date 2017-09-05 09:38:03
 * @since v1.0.0
 */
@Service
public class DayChangeReconService {
	private static DateTimeFormatter formatter = DateTimeFormat.forPattern(DateUtil.FORMAT_DATE);
	private Logger logger = LoggerFactory.getLogger(getClass());

	@Value("${DAY_RECON_BATCH_HANDLE_SIZE}")
	private Integer limitSize;

	@Resource
	private DayChangeReconDao dayChangeReconDao;
	@Resource
	private DayChangeRecordDao dayChangeRecordDao;
	@Resource
	private DayChangeReconDetailDao dayChangeReconDetailDao;

	/**
	 * 构建日切对账明细vo
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-10-20 17:44:30
	 * @params detail DayChangeReconDetail
	 * @since v1.0.0
	 */
	private DayChangeListVo buildDayChangeListVo(DayChangeReconDetail detail) {
		DayChangeListVo vo = new DayChangeListVo();
		Long amount = detail.getAmount() == null ? 0L : detail.getAmount();
		Long frozenAmount = detail.getFrozenAmount() == null ? 0L : detail.getFrozenAmount();
		Long fuiouAmount = detail.getFuiouAmount() == null ? 0L : detail.getFuiouAmount();
		Long fuoiuFrozenAmount = detail.getFuiouFrozenAmount() == null ? 0L : detail.getFuiouFrozenAmount();

		vo.setAccountNo(detail.getAccountNo());
		vo.setAccountName(detail.getAccountName());
		vo.setAccountAliasName(detail.getAccountAliasName());
		vo.setUserId(detail.getUserId());
		vo.setUserName(detail.getAccountName());
		vo.setFuAccountNo(detail.getDepositAccountNo());

		// 返回日期为 数据日期 = 日切日期-1
		vo.setDayChangeDate(formatter.print(formatter.parseDateTime(detail.getChangeDay()).minusDays(1)));
		// 本地余额,可为null
		vo.setAmount(detail.getAmount());
		// 本地冻结余额,可为null
		vo.setFrozenAmount(detail.getFrozenAmount());

		// 余额和冻结都为空，可用为null；如果一方不为空，将null转成0 进行计算
		if (detail.getAmount() == null && detail.getFrozenAmount() == null) {
			vo.setAvailableAmount(null);
		} else {
			vo.setAvailableAmount(amount - frozenAmount);
		}

		vo.setFuAmount(detail.getFuiouAmount());// 富友余额
		vo.setFuFrozenAmount(detail.getFuiouFrozenAmount());// 富友冻结余额

		if (detail.getFuiouAmount() == null && detail.getFuiouFrozenAmount() == null) {
			vo.setFuAvailableAmount(null);// 富友可用余额
		} else {
			vo.setFuAvailableAmount(fuiouAmount - fuoiuFrozenAmount);// 富友可用余额
		}

		vo.setAmountDifference(fuiouAmount - amount);// 账面余额差异
		vo.setFrozenAmountDifference(fuoiuFrozenAmount - frozenAmount);// 冻结月差异

		return vo;
	}

	/**
	 * 检查日期格式及范围
	 *
	 * @author liangqm
	 * @date 2017-10-20 17:04:19
	 * @params date
	 * @since v1.0.0
	 */
	private void checkDateFormatter(String date) {
		if (!DateUtil.isFormat(date, DateUtil.FORMAT_DATE)) { // 不符合日期格式 yyyy-MM-dd
			throw AccountExceptionCode.JOB_DATE_FORMAT_ERROR.exp(date);
		}

		DateTime jobDay = DateTime.parse(date, formatter);

		if (jobDay.isAfter(getNow())) { // 日期不能超过当前时间
			throw AccountExceptionCode.JOB_DATE_ERROR.exp(date);
		}
	}

	/**
	 * 异步日切对账计算
	 *
	 * @author liangqm
	 * @date 2017-10-20 16:05:38
	 * @params context
	 * @since v1.0.0
	 */
	@Async
	public void syncDoDayChangeRecon(JobContext context) {
		this.doDayChangeRecon(context);
		MDC.clear();
	}

	/**
	 * 日切对账计算
	 *
	 * @author liangqm
	 * @date 2017-10-20 16:05:38
	 * @params context
	 * @since v1.0.0
	 */
	public void doDayChangeRecon(JobContext context) {
		final String OP = "DayChangeReconDetailService.doDayChangeRecon";

		DateTime jobDay = JobContextUtils.getJobDay(context); // 默认日切日期为当前日期
		String changeDay = formatter.print(jobDay);// 对账任务日期

		logger.info(Log.op(OP).kv("changeDay", changeDay).msg("日切对账计算start").toString());

		// 获取最新执行成功的日切任务记录
		DayChangeRecord record = dayChangeRecordDao.queryLatestByDate(changeDay);

		// 查看日切任务完成情况，日切任务完成后才能执行对象，否则输出警告日志直接返回
		if ((record != null) && DayChangeStateEnum.SUCCESS.getValue().equals(record.getState()) && record.getChangeDay().equals(changeDay)) {
			// 处理对账任务
			handleReconTask(changeDay);
		} else {
			logger.warn(Log.op(OP).kv("changeDay", changeDay).msg("需要等日切操作进行完毕，才能执行该步骤").toString());
			return;
		}

		logger.info(Log.op(OP).kv("changeDay", changeDay).msg("日切对账计算end").toString());
	}

	/**
	 * 处理对账任务
	 * 1.清空指定对账任务日期对账详情数据和汇总数
	 * 2.插入本地对账详情数据
	 * 3.插入富友对账数据
	 * 4.计算对账汇总数据
	 *
	 * @author liangqm
	 * @date 2017-11-10 16:47:00
	 * @params changeDay
	 * @since v1.0.0
	 */
	private void handleReconTask(String changeDay) {
		final String OP = "DayChangeReconDetailService.handleReconTask";

		String preChangeDay = formatter.print(formatter.parseDateTime(changeDay).minusDays(1));// 对账任务日期前一天

		logger.info(Log.op(OP).kv("changeDay", changeDay).msg("开始处理对账任务").toString());

		// 1.清除指定日期对账数据
		logger.info(Log.op(OP).kv("changeDay", changeDay).msg("开始清除历史数据").toString());
		cleanData(changeDay);
		logger.info(Log.op(OP).kv("changeDay", changeDay).msg("清除历史数据完成").toString());

		try {
			// 2.插入本地对账数据
			logger.info(Log.op(OP).kv("changeDay", changeDay).msg("开始插入本地对账数据").toString());
			insertYyfaxReconData(changeDay, preChangeDay);
			logger.info(Log.op(OP).kv("changeDay", changeDay).msg("插入本地对账数据完成").toString());

			// 3.插入富友对账数据
			logger.info(Log.op(OP).kv("changeDay", changeDay).msg("开始插入富友对账数据").toString());
			insertFuiouReconData(changeDay, preChangeDay);
			logger.info(Log.op(OP).kv("changeDay", changeDay).msg("插入富友对账数据完成").toString());

		} catch (DuplicateKeyException e) {
			logger.warn(Log.op(OP).kv("changeDay", changeDay).msg("不能重复进行").toString(), e);
			return;
		} catch (Exception e) {
			logger.error(Log.op(OP).kv("changeDay", changeDay).msg("插入对账数据失败").toString(), e);
			throw AccountExceptionCode.DAY_CHANGE_RECON_INSERT_ERROR.exp(changeDay);
		}

		// 4.计算对账汇总数据
		calculateReconStatistics(changeDay);

	}

	/**
	 * 计算对账统计信息
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-11-10 17:27:30
	 * @params changeDay
	 * @since v1.0.0
	 */
	private void calculateReconStatistics(String changeDay) {
		final String OP = "DayChangeReconDetailService.calculateReconStatistics";

		// 汇总计算不用数据类型的 记录总数, 账面余额求和, 冻结金额求和
		List<DayChangeRecon> dayChangeReconList = dayChangeReconDao.countOfDayChange(changeDay);

		for (DayChangeRecon recon : dayChangeReconList) { // 每一种dataType(平台、个人、公司)一条记录

			recon.setNotEqualCount(recon.getRedundantCount() + recon.getFuiouRedundantCount()); // 计算友金所与富友记不同记录数
			recon.setNotEqualAmount(recon.getFuiouAmount() - recon.getAmount()); // 计算友金所与富有账面余额差值(FU-yyfax)
			recon.setNotEqualFrozenAmount(recon.getFuiouFrozenAmount() - recon.getFrozenAmount()); // 计算友金所与富有冻结月差值(FU-yyfax)
			recon.setChangeDay(changeDay); // 日切日期
			recon.setCreateTime(DateUtil.getCurrentDateTime()); // 创建记录时间

			try {
				dayChangeReconDao.insert(recon); // 插入汇总数据数据
			} catch (DuplicateKeyException e) {
				logger.warn(Log.op(OP).kv("changeDay", changeDay).kv("dataType", recon.getDataType()).msg("不能重复进行").toString(), e);
				continue;
			}
		}
	}

	/**
	 * 插入富友对账记录
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-11-10 17:21:00
	 * @params changeDay
	 * @params preChangeDay
	 * @since v1.0.0
	 */
	private void insertFuiouReconData(String changeDay, String preChangeDay) {
		final String OP = "DayChangeReconDetailService.insertFuiouReconData";

		// 1分批查询
		Integer queryId = 0;

		List<DayChangeReconDetail> reconDetails;
		boolean doQuery = true;
		while (doQuery) {
			// 分批查询
			reconDetails = dayChangeReconDetailDao.batchQueryFuiouRecon(preChangeDay, changeDay, queryId, limitSize);
			// 没有查到数据跳出循环
			if (CollectionUtils.isEmpty(reconDetails)) {
				break;
			}
			logger.info(Log.op(OP).kv("changeDay", changeDay).kv("queryId", queryId).kv("reconDetails size", reconDetails.size()).msg("查询富友对账记录").toString());

			// 2分批插入
			int insertSize = dayChangeReconDetailDao.batchInsertDayChangeReconDetails(reconDetails);
			logger.info(Log.op(OP).kv("changeDay", changeDay).kv("queryId", queryId).kv("reconDetails size", reconDetails.size()).msg("插入富友对账记录").toString());


			// 返回结果最后一个id 作为下一次查询id
			queryId = reconDetails.get(reconDetails.size() - 1).getId();
		}
	}

	/**
	 * 插入友金所日切对账记录
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-11-10 17:21:00
	 * @params changeDay
	 * @params preChangeDay
	 * @since v1.0.0
	 */
	private void insertYyfaxReconData(String changeDay, String preChangeDay) {
		final String OP = "DayChangeReconDetailService.insertYyfaxReconData";

		// 1分批查询
		Integer queryId = 0;
		List<DayChangeReconDetail> reconDetails;
		boolean doQuery = true;
		while (doQuery) {
			// 分批查询
			reconDetails = dayChangeReconDetailDao.batchQueryDayChangeRecon(preChangeDay, changeDay, queryId, limitSize);

			// 没有查到数据跳出循环
			if (CollectionUtils.isEmpty(reconDetails)) {
				break;
			}
			logger.info(Log.op(OP).kv("changeDay", changeDay).kv("queryId", queryId).kv("reconDetails size", reconDetails.size()).msg("查询友金所日切对账记录").toString());
			// 2分批插入
			int insertSize = dayChangeReconDetailDao.batchInsertDayChangeReconDetails(reconDetails);
			logger.info(Log.op(OP).kv("changeDay", changeDay).kv("queryId", queryId).kv("reconDetails size", reconDetails.size()).msg("插入友金所日切对账记录").toString());


			// 返回结果最后一个id 作为下一次查询id
			queryId = reconDetails.get(reconDetails.size() - 1).getId();
		}
	}

	/**
	 * 清理数据
	 *
	 * @author liangqm
	 * @date 2017-11-10 16:47:00
	 * @params changeDay
	 * @since v1.0.0
	 */
	private void cleanData(String changeDay) {
		dayChangeReconDetailDao.delReconDetail(changeDay);
		dayChangeReconDetailDao.delRecon(changeDay);
	}

	/**
	 * 计算查询日切日期
	 * 1.默认查询日切日期为当前日期
	 * 2.查询日切日期（T+1 00:00:00）= 查询日期(T 23:59:59) + 1天
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-10-20 16:47:00
	 * @params
	 * @since v1.0.0
	 */
	private String calculateDate(String queryDate) {
		String dayChangeDate;

		if (StringUtils.isBlank(queryDate)) {
			dayChangeDate = formatter.print(getNow()); // 默认查询日切日期
		} else {
			// check 日期格式 & 日期范围
			this.checkDateFormatter(queryDate);
			dayChangeDate = formatter.print(formatter.parseDateTime(queryDate).plusDays(1));// 日切日期=查询日期+1天
		}

		return dayChangeDate;
	}

	/**
	 * 查询日切对账明细列表(全部或差异列表)
	 * queryType 1全部数据，2当日异常数据
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-10-20 9:45:00
	 * @params params
	 * @since v1.0.0
	 */
	public Page<DayChangeListVo> queryAccountFundsList(DayChangeListQueryReq params) {
		String OP = "DayChangeReconService.queryAccountFundsList";
		String queryDate = this.calculateDate(params.getQueryDate());
		logger.info(Log.op(OP).kv("queryDate", queryDate).kv("request", params).msg("start").toString());

		List<DayChangeListVo> voList = Lists.newArrayList();
		PageAdapter<DayChangeReconDetail> pageAdapter = dayChangeReconDetailDao.queryDayChangeReconDetailsByPage(params.getFuAccountNo(), queryDate,
				params.getDayChangeType(), params.getPageNo(), params.getPageSize(), params.getQueryType());

		for (DayChangeReconDetail detail : pageAdapter) { // 拼装返回结果集合
			voList.add(buildDayChangeListVo(detail));
		}

		return new Page<>(pageAdapter.getIndex(), pageAdapter.getSize(), pageAdapter.getTotalRecord(), voList);
	}

	/**
	 * 查询平台账户日切历史记录
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-10-20 9:53:10
	 * @params params
	 * @since v1.0.0
	 */
	public Page<DayChangeListVo> queryAccountRecords(DayChangeAccountRecordQueryReq params) {
		String OP = "DayChangeReconService.queryAccountRecords";
		String startDate = StringUtils.isBlank(params.getStartDate()) ? null : params.getStartDate();
		String endDate = StringUtils.isBlank(params.getEndDate()) ? null : params.getEndDate();

		// 按日切时间查询，日切时间比数据时间晚一天，查询时间+1天
		if (StringUtils.isNotBlank(startDate)) {
			startDate = this.calculateDate(startDate);
		}
		if (StringUtils.isNotBlank(endDate)) {
			endDate = this.calculateDate(endDate);
		}

		logger.info(Log.op(OP).kv("startDate", startDate).kv("endDate", endDate).kv("params", params).msg("start").toString());

		List<DayChangeListVo> voList = Lists.newArrayList();
		PageAdapter<DayChangeReconDetail> pageAdapter = dayChangeReconDetailDao.queryAccountRecords(params.getAccountNo(), startDate, endDate,
				params.getPageNo(), params.getPageSize());

		for (DayChangeReconDetail detail : pageAdapter) { // 拼装返回结果集合
			voList.add(buildDayChangeListVo(detail));
		}

		return new Page<>(pageAdapter.getIndex(), pageAdapter.getSize(), pageAdapter.getTotalRecord(), voList);
	}

	/**
	 * 查询日切对账 账面差额 和 冻结差额
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-10-20 9:39:15
	 * @params params
	 * @since v1.0.0
	 */
	public DayChangeDifferenceCountVo queryDayChangeDifferenceCount(DayChangeDifferenceCountQueryReq params) {
		String OP = "DayChangeReconService.queryDayChangeDifferenceCount";
		String queryDate = calculateDate(params.getQueryDate());

		logger.info(Log.op(OP).kv("queryDate", queryDate).kv("dataType", params.getDayChangeType()).msg("start").toString());

		DayChangeDifferenceCountVo vo = new DayChangeDifferenceCountVo();
		vo.setDayChangeDate(formatter.print(formatter.parseDateTime(queryDate).minusDays(1)));

		DayChangeRecon recon = dayChangeReconDao.queryDayChangeReconByDate(queryDate, params.getDayChangeType());

		if (recon == null) {// 返回0
			logger.warn(Log.op(OP).kv("queryDate", queryDate).kv("dataType", params.getDayChangeType()).msg("查询日切对账差额统计为NULL").toString());
			vo.setFrozenAmountDifference(0L);
			vo.setAmountDifference(0L);
			return vo;
		}

		vo.setAmountDifference(recon.getNotEqualAmount());
		vo.setFrozenAmountDifference(recon.getNotEqualFrozenAmount());
		return vo;
	}

	/**
	 * 查询日切对账总记录数和差异记录数
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-10-20 9:34:19
	 * @params params
	 * @since v1.0.0
	 */
	public DayChangeRecordCountVo queryDayChangeRecordCount(DayChangeRecordCountQueryReq params) {
		String OP = "DayChangeReconService.queryDayChangeRecordCount";
		String queryDate = calculateDate(params.getQueryDate());

		logger.info(Log.op(OP).kv("queryDate", queryDate).msg("start").toString());

		DayChangeRecordCountVo vo = new DayChangeRecordCountVo();
		vo.setDayChangeDate(formatter.print(formatter.parseDateTime(queryDate).minusDays(1))); // 数据时间= 日切时间-1天

		DayChangeRecon recon = dayChangeReconDao.queryDayChangeReconByDate(queryDate, params.getDayChangeType());
		if (recon == null) {// 查询结果为空，返回0
			logger.warn(Log.op(OP).kv("queryDate", queryDate).msg("统计日切对账记录NULL").toString());
			vo.setYyRecordCount(0L);
			vo.setFuRecordCount(0L);
			vo.setNotEqualCount(0L);
			return vo;
		}

		vo.setYyRecordCount(recon.getTotalCount());
		vo.setFuRecordCount(recon.getFuiouTotalCount());
		vo.setNotEqualCount(recon.getNotEqualCount());
		return vo;
	}

	private DateTime getNow() {
		return DateTime.now().toDateMidnight().toDateTime();
	}

	/**
	 * 查询客户对账汇总
	 *
	 * @return
	 * @author liangqm
	 * @date 2017-10-20 9:45:00
	 * @params params
	 * @since v1.0.0
	 */
	public Page<DayChangeCustomerFundsVo> queryCustomerFunds(DayChangeCustomerFundsQueryReq params) {
		String OP = "DayChangeReconService.queryCustomerFunds";

		String startDate = StringUtils.isBlank(params.getStartChangeDay()) ? null : params.getStartChangeDay();
		String endDate = StringUtils.isBlank(params.getEndChangeDay()) ? null : params.getEndChangeDay();

		// 按日切时间查询，日切时间比数据时间晚一天，查询时间+1天
		if (StringUtils.isNotBlank(startDate)) {
			startDate = this.calculateDate(startDate);
		}
		if (StringUtils.isNotBlank(endDate)) {
			endDate = this.calculateDate(endDate);
		}

		logger.info(Log.op(OP).kv("startDate", startDate).kv("endDate", endDate).msg("start").toString());

		List<DayChangeCustomerFundsVo> voList = Lists.newArrayList();
		PageAdapter<DayChangeCustomerFundsVo> pageAdapter = dayChangeReconDetailDao.queryCustomerFundsByPage(startDate, endDate, params.getPageNo(),
				params.getPageSize());

		for (DayChangeCustomerFundsVo vo : pageAdapter) { // 拼装返回结果集合
			vo.setChangeDay(formatter.print(formatter.parseDateTime(vo.getChangeDay()).minusDays(1)));
			voList.add(vo);
		}

		logger.info(Log.op(OP).kv("startDate", params.getStartChangeDay()).kv("endDate", params.getEndChangeDay()).msg("end").toString());
		return new Page<>(pageAdapter.getIndex(), pageAdapter.getSize(), pageAdapter.getTotalRecord(), voList);
	}
}